package mirrormonkey.core.module;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import mirrormonkey.core.EntityLifecycleListener;
import mirrormonkey.core.annotations.CoreModulePresets;
import mirrormonkey.core.messages.EntityChangeMessage;
import mirrormonkey.core.messages.EntityEndMessage;
import mirrormonkey.core.messages.EntityInitMessage;
import mirrormonkey.core.messages.TimeSyncRequestMessage;
import mirrormonkey.core.messages.TimeSyncResponseMessage;
import mirrormonkey.framework.ConnectionInfo;
import mirrormonkey.framework.EntityProvider;
import mirrormonkey.framework.SyncAppState;
import mirrormonkey.framework.entity.EntityData;
import mirrormonkey.framework.entity.StaticEntityData;
import mirrormonkey.framework.entity.SyncEntity;
import mirrormonkey.framework.module.MirrorMonkeyModule;
import mirrormonkey.util.listeners.ListenerConfiguration;

import com.jme3.network.MessageConnection;
import com.jme3.network.serializing.Serializer;

public abstract class CoreModule extends MirrorMonkeyModule {

	protected static final Class<?>[] LIFECYCLE_MESSAGES = {
			EntityInitMessage.class, EntityEndMessage.class,
			EntityChangeMessage.class };

	private final ListenerConfiguration listenerConfiguration;

	protected final Map<Class<?>, ListenerConfiguration> specificListeners;

	private final EntityProvider entityProvider;

	protected final TimeSyncRequestListener syncRequestListener;

	protected final TimeSyncResponseListener syncResponseListener;

	private final SortedSet<EntityLifecycleListener> collectListenersListSingleton;

	private final SortedSet<EntityLifecycleListener> returnListenersListSingleton;

	public CoreModule(SyncAppState appState) {
		super(appState);

		listenerConfiguration = new ListenerConfiguration();

		appState.entityProvider.parsePresetClass(CoreModulePresets.class);
		entityProvider = appState.entityProvider;

		for (Class<?> i : LIFECYCLE_MESSAGES) {
			Serializer.registerClass(i);
		}

		Serializer.registerClass(TimeSyncRequestMessage.class);
		Serializer.registerClass(TimeSyncResponseMessage.class);

		specificListeners = new HashMap<Class<?>, ListenerConfiguration>();

		syncRequestListener = new TimeSyncRequestListener(appState);
		appState.eventManager.addMessageListener(syncRequestListener,
				TimeSyncRequestMessage.class);

		syncResponseListener = new TimeSyncResponseListener(this);
		appState.eventManager.addMessageListener(syncResponseListener,
				TimeSyncResponseMessage.class);

		appState.modules.put(CoreModule.class, this);

		collectListenersListSingleton = new TreeSet<EntityLifecycleListener>(
				new ListenerOrderComparator());
		returnListenersListSingleton = Collections
				.unmodifiableSortedSet(collectListenersListSingleton);
	}

	public ListenerConfiguration getListenerConfiguration(Class<?> notifyClass) {
		return specificListeners.get(notifyClass);
	}

	public void addEntityLifecycleListener(EntityLifecycleListener listener,
			Class<?>... notifyClasses) {
		for (Class<?> i : notifyClasses) {
			ListenerConfiguration previous = specificListeners.get(i);
			if (previous == null) {
				previous = new ListenerConfiguration();
				specificListeners.put(i, previous);
			}
			previous.addListener(listener);
		}
	}

	public void removeEntityLifecycleListener(EntityLifecycleListener listener,
			Class<?>... notifyClasses) {
		for (Class<?> i : notifyClasses) {
			ListenerConfiguration previous = specificListeners.get(i);
			if (previous != null) {
				previous.removeListener(listener);
				if (previous.isEmpty()) {
					specificListeners.remove(i);
				}
			}
		}
	}

	protected Collection<EntityLifecycleListener> collectListeners(
			EntityData entityData, StaticEntityData staticEntityData,
			ConnectionInfo info) {
		collectListenersListSingleton.clear();
		getListenerConfiguration().getListeners(EntityLifecycleListener.class,
				collectListenersListSingleton);
		info.collectListeners(collectListenersListSingleton);
		entityData
				.collectEntityLifecycleListeners(collectListenersListSingleton);
		if (staticEntityData != null) {
			for (Class<?> c : staticEntityData.notifyClasses) {
				ListenerConfiguration lc = specificListeners.get(c);
				if (lc != null) {
					lc.getListeners(EntityLifecycleListener.class,
							collectListenersListSingleton);
				}
			}
		}
		return returnListenersListSingleton;
	}

	public void notifyInitListeners(EntityData entityData,
			StaticEntityData staticData, ConnectionInfo info) {
		for (EntityLifecycleListener i : collectListeners(entityData,
				staticData, info)) {
			i.entityInitialized(entityData.getEntity(), info);
		}
	}

	public void notifyChangeListeners(EntityData newEntityData,
			StaticEntityData oldStaticData, StaticEntityData newStaticData,
			SyncEntity oldEntity, ConnectionInfo info) {
		for (EntityLifecycleListener i : collectListeners(newEntityData,
				oldStaticData, info)) {
			i.entityReplaced(oldEntity, oldStaticData,
					newEntityData.getEntity(), info);
		}
		for (EntityLifecycleListener i : collectListeners(newEntityData,
				newStaticData, info)) {
			i.entityReplacing(oldEntity, oldStaticData,
					newEntityData.getEntity(), info);
		}
	}

	public void notifyEndListeners(EntityData data,
			StaticEntityData staticData, ConnectionInfo info) {
		for (EntityLifecycleListener i : collectListeners(data, staticData,
				info)) {
			i.entityRemoved(data.getEntity(), info);
		}
	}

	public abstract ConnectionInfo getData(MessageConnection source);

	public EntityProvider getEntityProvider() {
		return entityProvider;
	}

	public ListenerConfiguration getListenerConfiguration() {
		return listenerConfiguration;
	}

	private final class ListenerOrderComparator implements
			Comparator<EntityLifecycleListener> {

		@Override
		public int compare(EntityLifecycleListener o1,
				EntityLifecycleListener o2) {
			if (o1.getSortingOrder() != o2.getSortingOrder()) {
				return o1.getSortingOrder() - o2.getSortingOrder();
			}
			return System.identityHashCode(o1) - System.identityHashCode(o2);
		}

	}
}